﻿namespace TNet
{
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.InteropServices;
    using UnityEngine;

    public class UdpProtocol
    {
        public static IPAddress defaultNetworkInterface = null;
        private IPEndPoint mBroadcastEndPoint = new IPEndPoint(IPAddress.Broadcast, 0);
        private static EndPoint mDefaultEndPoint;
        private EndPoint mEndPoint;
        protected Queue<Datagram> mIn = new Queue<Datagram>();
        private bool mMulticast = true;
        private IPEndPoint mMulticastEndPoint = new IPEndPoint(multicastIP, 0);
        protected Queue<Datagram> mOut = new Queue<Datagram>();
        private int mPort = -1;
        private Socket mSocket;
        private byte[] mTemp = new byte[0x2000];
        private static IPAddress multicastIP = IPAddress.Parse("224.168.100.17");
        public static bool useMulticasting = true;

        public void Broadcast(TNet.Buffer buffer, int port)
        {
            if (buffer != null)
            {
                buffer.MarkAsUsed();
                IPEndPoint point = !this.mMulticast ? this.mBroadcastEndPoint : this.mMulticastEndPoint;
                point.Port = port;
                try
                {
                    this.mSocket.SendTo(buffer.buffer, buffer.position, buffer.size, SocketFlags.None, point);
                }
                catch (Exception exception)
                {
                    this.Error(null, exception.Message);
                }
                buffer.Recycle();
            }
        }

        public void Error(IPEndPoint ip, string error)
        {
            TNet.Buffer buffer = TNet.Buffer.Create();
            buffer.BeginPacket(Packet.Error).Write(error);
            buffer.EndTcpPacketWithOffset(4);
            Datagram item = new Datagram {
                buffer = buffer,
                ip = ip
            };
            Queue<Datagram> mIn = this.mIn;
            lock (mIn)
            {
                this.mIn.Enqueue(item);
            }
        }

        private void OnReceive(IAsyncResult result)
        {
            if (this.isActive)
            {
                int count = 0;
                try
                {
                    count = this.mSocket.EndReceiveFrom(result, ref this.mEndPoint);
                }
                catch (Exception exception)
                {
                    this.Error(new IPEndPoint(Tools.localAddress, 0), exception.Message);
                }
                if (count > 4)
                {
                    TNet.Buffer buffer = TNet.Buffer.Create();
                    buffer.BeginWriting(false).Write(this.mTemp, 0, count);
                    buffer.BeginReading(4);
                    Datagram item = new Datagram {
                        buffer = buffer,
                        ip = (IPEndPoint) this.mEndPoint
                    };
                    Queue<Datagram> mIn = this.mIn;
                    lock (mIn)
                    {
                        this.mIn.Enqueue(item);
                    }
                }
                if (this.mSocket != null)
                {
                    this.mEndPoint = mDefaultEndPoint;
                    this.mSocket.BeginReceiveFrom(this.mTemp, 0, this.mTemp.Length, SocketFlags.None, ref this.mEndPoint, new AsyncCallback(this.OnReceive), null);
                }
            }
        }

        private void OnSend(IAsyncResult result)
        {
            if (this.isActive)
            {
                int num = 0;
                try
                {
                    num = this.mSocket.EndSendTo(result);
                }
                catch (Exception exception)
                {
                    num = 1;
                    Debug.Log("[TNet] OnSend: " + exception.Message);
                }
                Queue<Datagram> mOut = this.mOut;
                lock (mOut)
                {
                    this.mOut.Dequeue().buffer.Recycle();
                    if (((num > 0) && (this.mSocket != null)) && (this.mOut.Count != 0))
                    {
                        Datagram datagram = this.mOut.Peek();
                        this.mSocket.BeginSendTo(datagram.buffer.buffer, datagram.buffer.position, datagram.buffer.size, SocketFlags.None, datagram.ip, new AsyncCallback(this.OnSend), null);
                    }
                }
            }
        }

        public bool ReceivePacket(out TNet.Buffer buffer, out IPEndPoint source)
        {
            if (this.mPort == 0)
            {
                this.Stop();
                throw new InvalidOperationException("You must specify a non-zero port to UdpProtocol.Start() before you can receive data.");
            }
            if (this.mIn.Count != 0)
            {
                Queue<Datagram> mIn = this.mIn;
                lock (mIn)
                {
                    Datagram datagram = this.mIn.Dequeue();
                    buffer = datagram.buffer;
                    source = datagram.ip;
                    return true;
                }
            }
            buffer = null;
            source = null;
            return false;
        }

        public void Send(TNet.Buffer buffer, IPEndPoint ip)
        {
            if (ip.Address.Equals(IPAddress.Broadcast))
            {
                this.Broadcast(buffer, ip.Port);
            }
            else
            {
                buffer.MarkAsUsed();
                if (this.mSocket != null)
                {
                    buffer.BeginReading();
                    Queue<Datagram> mOut = this.mOut;
                    lock (mOut)
                    {
                        Datagram item = new Datagram {
                            buffer = buffer,
                            ip = ip
                        };
                        this.mOut.Enqueue(item);
                        if (this.mOut.Count == 1)
                        {
                            this.mSocket.BeginSendTo(buffer.buffer, buffer.position, buffer.size, SocketFlags.None, ip, new AsyncCallback(this.OnSend), null);
                        }
                    }
                }
                else
                {
                    buffer.Recycle();
                    throw new InvalidOperationException("The socket is null. Did you forget to call UdpProtocol.Start()?");
                }
            }
        }

        public void SendEmptyPacket(IPEndPoint ip)
        {
            TNet.Buffer buffer = TNet.Buffer.Create(false);
            buffer.BeginPacket(Packet.Empty);
            buffer.EndPacket();
            this.Send(buffer, ip);
        }

        public bool Start()
        {
            return this.Start(0);
        }

        public bool Start(int port)
        {
            this.Stop();
            this.mPort = port;
            this.mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            this.mSocket.MulticastLoopback = true;
            this.mMulticast = useMulticasting;
            if (useMulticasting)
            {
                IEnumerator<IPAddress> enumerator = Tools.localAddresses.GetEnumerator();
                try
                {
                    while (enumerator.MoveNext())
                    {
                        IPAddress current = enumerator.Current;
                        MulticastOption optionValue = new MulticastOption(multicastIP, current);
                        try
                        {
                            this.mSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, optionValue);
                            continue;
                        }
                        catch (Exception exception)
                        {
                            Debug.LogWarning(exception.Message);
                            continue;
                        }
                    }
                }
                finally
                {
                    if (enumerator == null)
                    {
                    }
                    enumerator.Dispose();
                }
            }
            else
            {
                this.mSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            }
            if (this.mPort != 0)
            {
                try
                {
                    IPAddress defaultNetworkInterface;
                    if (UdpProtocol.defaultNetworkInterface != null)
                    {
                        defaultNetworkInterface = UdpProtocol.defaultNetworkInterface;
                    }
                    else
                    {
                        defaultNetworkInterface = IPAddress.Any;
                    }
                    this.mEndPoint = new IPEndPoint(defaultNetworkInterface, 0);
                    mDefaultEndPoint = new IPEndPoint(defaultNetworkInterface, 0);
                    this.mSocket.Bind(new IPEndPoint(defaultNetworkInterface, this.mPort));
                    this.mSocket.BeginReceiveFrom(this.mTemp, 0, this.mTemp.Length, SocketFlags.None, ref this.mEndPoint, new AsyncCallback(this.OnReceive), null);
                }
                catch (Exception)
                {
                    this.Stop();
                    return false;
                }
            }
            return true;
        }

        public void Stop()
        {
            this.mPort = -1;
            if (this.mSocket != null)
            {
                this.mSocket.Close();
                this.mSocket = null;
            }
            TNet.Buffer.Recycle(this.mIn);
            TNet.Buffer.Recycle(this.mOut);
        }

        public bool isActive
        {
            get
            {
                return (this.mPort != -1);
            }
        }

        public int listeningPort
        {
            get
            {
                return ((this.mPort <= 0) ? 0 : this.mPort);
            }
        }
    }
}

